#ifndef RAY_TRACE_PRO_GI_H
#define RAY_TRACE_PRO_GI_H

#include "Windows/GxThreads.h"
#include "Basic/GxLibBasic.h"
#include "BBox.h"
#include "CameraProjection.h"

namespace RayTracePro
{
	using namespace GxLibMath;
	using namespace GxLibBasic;
	
	class RayTraceSystem;
	class Scene;

	class DirectionLookupTable
	{
	private:
		float cosTheta[256], sinTheta[256], cosPhi[256], sinPhi[256];
	public:
		DirectionLookupTable()
		{
			for (int i = 0; i<256; i++)
			{
				double theta = double(i)*(1.0/255.0)*PI;
				double phi = 2.0*double(i)*(1.0/256.0)*PI;
				cosTheta[i] = (float)cos(theta);
				sinTheta[i] = (float)sin(theta);
				cosPhi[i] = (float)cos(phi);
				sinPhi[i] = (float)sin(phi);
			}
		}
		__forceinline void Convert(Vec3 & dir, int u, int v)
		{
			dir.y = cosTheta[v];
			dir.x = sinTheta[v]*cosPhi[u];
			dir.z = sinTheta[v]*sinPhi[u];
		}
	};

	struct Photon
	{
		Vec3 Position;
		unsigned char Power[4];
		unsigned char Direction[2];
		unsigned char Normal;
		unsigned char Flags;
		__forceinline void SetSplitDimension(int dim)
		{
			Flags = dim;
		}

		__forceinline int GetSplitDimension()
		{
			return Flags;
		}

		__forceinline void GetPower(Vec4 & power)
		{
			double f;
			if (Power[3])
			{
				f = ldexp(1.0,Power[3]-(int)(128+8));
				power.x = (float)(Power[0] * f);
				power.y = (float)(Power[1] * f);
				power.z = (float)(Power[2] * f);
				power.w = 1.0f;
			}
			else
				power.SetZero();
		}
		__forceinline void SetPower(const Vec4 & power)
		{
			double v;
			int e;
			v = power.x;
			if (power.y > v) v = power.y;
			if (power.z > v) v = power.z;
			if (v < 1e-32)
			{
				Power[0] = Power[1] = Power[2] = Power[3] = 0;
			}
			else 
			{
				v = frexp(v,&e) * 256.0/v;
				Power[0] = (unsigned char) (power.x * v);
				Power[1] = (unsigned char) (power.y * v);
				Power[2] = (unsigned char) (power.z * v);
				Power[3] = (unsigned char) (e + 128);
			}
		}
		__forceinline void GetDirection(Vec3 & dir, DirectionLookupTable & table)
		{
			table.Convert(dir, Direction[0], Direction[1]);
		}
		__forceinline void SetDirection(const Vec3 & dir)
		{
			float u,v;
			v = acos(dir.y);
			u = atan2(dir.z, dir.x);
			if (u<0.0f)
				u += PI * 2.0f;
			Direction[0] = (unsigned char)(u / (2*PI) * 256.0f);
			Direction[1] = (unsigned char)(v / PI * 255.0f);
		}
		__forceinline void SetNormal(const Vec3 & dir)
		{
			float u,v;
			v = acos(dir.y);
			u = atan2(dir.z, dir.x);
			if (u<0.0f)
				u += PI * 2.0f;
			int d0 = (int)(u / (2*PI) * 256.0f);
			int d1 = (int)(v / PI * 15.0f);
			d0 = d0 & 0xF0 | d1;
			Normal = d0;
		}
		__forceinline void GetNormal(Vec3 & dir, DirectionLookupTable & table)
		{
			int d0 = Normal & 0xF0;
			int d1 = (Normal & 0x0F)<<4;
			table.Convert(dir, d0, d1);
		}
	};

	typedef Photon IrradiancePhoton;
	
	struct Importon
	{
		Vec3 Position;
		Vec3 Normal;
	};

	class PhotonMap
	{
	public:
		struct PhotonSearchResult
		{
			int HeapCreated; 
			Array<float, true> Distances; 
			Array<Photon *, true> Photons; 
			PhotonSearchResult(int maxPhotons)
			{
				Distances.SetCapacity(maxPhotons);
				Photons.SetCapacity(maxPhotons);
			}
		};
	private:
		GxWinSystem::Threading::SpinLock lock;
		DirectionLookupTable dirTable;
		int lastScale;
		int halfStoredPhotons;
		int halfIrradiancePhotons;
		BBox bbox;
		void SearchPhotons(PhotonSearchResult & rs, const Vec3 & position, float radius);
	public:
		class PhotonCollectionBuffer
		{
			friend class PhotonMap;
		private:
			Array<Photon, true> buffer;
			PhotonMap * collection;
		public:
			inline void Add(const Photon & p)
			{
				if (buffer.Count() == buffer.GetCapacity())
				{
					collection->AddBuffer(*this);
					buffer.Clear();
				}
				buffer.Add(p);
			}
		};
	public:
		Array<Photon, true> Photons;
		Array<IrradiancePhoton, true> Irradiances;
		PhotonMap(int capacity)
		{
			lastScale = 0;
			// Add placeholder photon
			Photons.SetCapacity(capacity);
			Photons.Add(Photon());
		}
		inline void AddBuffer(const PhotonCollectionBuffer & buffer)
		{
			lock.Lock();
			Photons.Add(buffer.buffer);
			lock.Unlock();
		}
		inline void CreatePhotonCollectionBuffer(PhotonCollectionBuffer & buffer, int bufferLength)
		{
			buffer.collection = this;
			buffer.buffer.SetCapacity(bufferLength);
		}
		inline void ScalePhotons(float scale)
		{
			for (int i = lastScale; i<Photons.Count(); i++)
			{
				Vec4 power;
				Photons[i].GetPower(power);
				Vec4::Scale(power, power, scale);
				Photons[i].SetPower(power);
			}
			lastScale = Photons.Count();
		}
		void Balance();
		void PrecomputeIrradiance(Array<Importon, true>& importons, Scene * scene, int photonsPerEstimate, float radius);
		void EstimateIrradianceOriginal(Vec3 & rs, PhotonSearchResult & buffer, const Vec3 & pos, const Vec3 & normal, float maxRadius);
		void EstimateIrradiance(Vec3 & rs, PhotonSearchResult & buffer, const Vec3 & pos, const Vec3 & normal, float maxRadius);

	};
	
	void EmitImportons(Scene * scene, Array<Importon, true> & importons, CameraProjection & proj);
}

#endif